/*
 * Copyright 2019 the go-netty project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tcp

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/go-netty/go-netty/transport"
	"net"
)

// New tcp factory
func NewTLS() transport.Factory {
	return new(tcpFactoryTLS)
}

type tcpFactoryTLS struct{}

func (*tcpFactoryTLS) Schemes() transport.Schemes {
	return transport.Schemes{"tcp", "tcp4", "tcp6"}
}

func (f *tcpFactoryTLS) Connect(options *transport.Options) (transport.Transport, error) {

	if err := f.Schemes().FixedURL(options.Address); nil != err {
		return nil, err
	}

	tcpOptions := FromContext(options.Context, DefaultOption)

	tcpOptions.TlsConfig.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
		if t, ok := info.Conn.(*net.TCPConn); ok {
			if err := t.SetKeepAlive(tcpOptions.KeepAlive); nil != err {
				return nil, err
			}

			if err := t.SetKeepAlivePeriod(tcpOptions.KeepAlivePeriod); nil != err {
				return nil, err
			}

			if err := t.SetLinger(tcpOptions.Linger); nil != err {
				return nil, err
			}

			if err := t.SetNoDelay(tcpOptions.NoDelay); nil != err {
				return nil, err
			}

			if tcpOptions.SockBuf > 0 {
				if err := t.SetReadBuffer(tcpOptions.SockBuf); nil != err {
					return nil, err
				}

				if err := t.SetWriteBuffer(tcpOptions.SockBuf); nil != err {
					return nil, err
				}
			}
		} else {
			fmt.Println("TLS over non-TCP connection")
		}

		return tcpOptions.TlsConfig, nil
	}

	var d = net.Dialer{Timeout: tcpOptions.Timeout}
	conn, err := tls.DialWithDialer(&d, options.Address.Scheme, options.Address.Host, tcpOptions.TlsConfig)
	if nil != err {
		return nil, err
	}

	return &tcpTransportTLS{Conn: conn}, nil
}

func (f *tcpFactoryTLS) Listen(options *transport.Options) (transport.Acceptor, error) {

	if err := f.Schemes().FixedURL(options.Address); nil != err {
		return nil, err
	}

	tcpOptions := FromContext(options.Context, DefaultOption)
	if tcpOptions.TlsConfig == nil {
		return nil, errors.New("ignore set the TLS config")
	}

	l, err := tls.Listen(options.Address.Scheme, options.AddressWithoutHost(), tcpOptions.TlsConfig)
	if nil != err {
		return nil, err
	}

	return &tcpAcceptorTLS{listener: l, options: FromContext(options.Context, DefaultOption)}, nil
}

type tcpAcceptorTLS struct {
	listener net.Listener
	options  *Options
}

func (t *tcpAcceptorTLS) Accept() (transport.Transport, error) {

	conn, err := t.listener.Accept()
	if nil != err {
		return nil, err
	}

	return &tcpTransportTLS{Conn: conn.(*tls.Conn)}, nil
}

func (t *tcpAcceptorTLS) Close() error {
	if t.listener != nil {
		defer func() { t.listener = nil }()
		return t.listener.Close()
	}
	return nil
}
